
import mimetypes
import sys
import pprint
import traceback
import wsgiref.handlers
from google.appengine.ext import webapp
from google.appengine.api import users
from google.appengine.ext.webapp import template
from cStringIO import StringIO
import logging
import os
from django.utils import simplejson
from xml.etree import ElementTree as ET    
from pypione import net
from pypione.exc import *
from pypione.model import *

log = logging.getLogger()

def tpl_path(template_file_name):
    return os.path.join(os.path.dirname(__file__), 'templates', template_file_name)

class Download(webapp.RequestHandler):
    def get(self, name, filestub):
        # probably can use urllib here...
        parts = filestub.split('?')
        filename = parts[0]
        p = PackageFile.all().filter("filename =", filename).fetch(1)[0]
        mtype, encoding = mimetypes.guess_type(p.filename)
        if not mtype:
            if p.filename.endswith('.egg'):
                mtype = 'application/zip'
            else:
                mtype = 'application/octet-steam'
        self.response.headers['Content-type'] = mtype
        self.response.headers['Content-Length'] = p.bytes
        #response.headers['Last-Modified'] = cherrypy.lib.http.HTTPDate(st.st_mtime)
        self.response.out.write(p.data)
            
class Index(webapp.RequestHandler):
    def get(self):
        tpl = { 'packages': [(p.name, p.version) for p in Package.all().order('-last_modified').fetch(75)] }
        self.response.out.write(template.render(tpl_path('index.html'), tpl))

class Fetch(webapp.RequestHandler):
    pypi_index_url = 'http://pypi.python.org/pypi'
    def get(self, package, release):
        self.response.headers['Content-type'] = 'application/json'
        fetch_result = {'successful': False}
        try:
            if package == 'p':
                raise ValueError("'p' is reserved for pypi and can't be used as a package name")
            server = net.xmlrpclib.Server(self.pypi_index_url, transport=net.FetchTransport())
            log.info("querying pypi for %s ...", package)
            if not release:
                releases = server.package_releases(package)
            else:
                releases = [release]
            log.info("%s releases: %s", package, releases)
            
            fetch_result['name'] = package
            fetch_result['packages'] = []
            fetch_result['releases'] = releases
            
            found = False
            for release in releases:
                for info in server.package_urls(package, release):
                    found = True
                    p = PackageFile.new(package, release, info['filename'], info['url'],
                            md5_digest=info['md5_digest'], packagetype=info['packagetype'], 
                            size=info['size'], python_version=info['python_version'])
                    p.save()
                    fetch_result['packages'].append(
                        dict(name=package, release=release, filename=info['filename'])
                    )
                if not found:
                    info = server.release_data(package, release)
                    if info:
                        if info['download_url'] in ('UNKNOWN', ''):
                            # i.e. http://pypi.python.org/pypi/fortune/0.4
                            #      http://pypi.python.org/pypi/pylon/0.0.1
                            break
                        found = True
                        filename = info['download_url']
                        filename = filename.split('?')[0]
                        filename = filename.split('#')[0]
                        filename = os.path.basename(filename)
                        p = PackageFile.new(package, release, filename, info['download_url'])
                        p.save()
                        fetch_result['packages'].append(
                            dict(name=package, release=release, filename=filename)
                        )
                        # hmm... will we ever see multiple fils in external URLs?  for now, break
                        break
        except Exception, e:
            log.exception("")
            exc_type, val, tb = sys.exc_info()
            fetch_result['successful'] = False
            fetch_result['exception'] = dict(
                exc_type=exc_type.__name__,
                value=str(val),
                tb=traceback.format_tb(tb)
            )
        else:
            if not found:
                fetch_result['successful'] = False
            else:
                fetch_result['successful'] = True
        simplejson.dump(fetch_result, self.response.out)

class QueueHandler(webapp.RequestHandler):
    def respond_to_get(self):
        raise NotImplementedError("this must return a dict response")
        
    def get(self, *args, **kw):
        ipaddr = self.request.environ.get('REMOTE_ADDR', None)
        if not QueueAllow.ip_is_allowed(ipaddr):
            self.error(403)
            self.response.headers['Content-type'] = 'text/plain'
            self.response.out.write("Forbidden.  Yellow cabs never cross White Ave.")
            return
        self.response.headers['Content-type'] = 'application/json'
        response = self.respond_to_get(*args, **kw)
        simplejson.dump(response, self.response.out)
    
class QueueFetchLater(QueueHandler):
    """queue a package to be fetched later
    
    /p/queue/fetch-later/r/<package>/
    /p/queue/fetch-later/r/<package>/<version>/
    
    Might respond like:
    
    <response status="success">
        <AddedToQueue/>
    </response>
    
    <response status="failure">
        <AlreadyInQueue/>
    </response>
    """
    def respond_to_get(self, package, version):
        response = ET.Element('response')
        q = Queue(  channel='fetch-later', 
                    name=package, 
                    version=version)
        q.save()
        return dict(
            successful=True,
            action='added-to-queue',
            name=package,
            version=version)
        
class QueueFetchInChunks(QueueHandler):
    def respond_to_get(self, md5_digest, byte_size, package, version):
        response = ET.Element('response')
        chunksize = 1000
        chunks = int(round(float(byte_size)/float(chunksize)))
        start, end = 0, chunksize-1
        for chunk in range(chunks):
            q = Queue(  channel='fetch-in-chunks', 
                        md5_digest=md5_digest, 
                        byte_range="%s-%s" % (start, end),
                        name=package,
                        version=version)
            q.save()
            start += chunksize
            end += chunksize
        
        return dict(
            successful=True,
            name=package,
            version=version,
            action='added-to-queue')
        
class QueuePending(QueueHandler):
    def respond_to_get(self, channel):
        assert channel != ''
        q = Queue.pending(channel)
        limit = 500
        response = dict(
            successful=True,
            count=str(q.count(limit)),
            channel=channel,
            packages=[])
            
        for pkg in q.fetch(limit):
            pkg_info = {}
            for name in pkg.dynamic_properties():
                pkg_info[name] = getattr(pkg, name)
            response['packages'].append(pkg_info)
        
        return response

class QueueFixPackagesPending(QueueHandler):
    def respond_to_get(self):
        q = Package.all()
        # cannot filter("name =", None)
        # I think this is a bug since this says you can:
        # http://code.google.com/appengine/docs/datastore/queriesandindexes.html#Restrictions_on_Queries
        limit = 500
        response = dict(
            successful=True,
            items=[]
        )
        num_need_fixing = 0
        for p in q.fetch(limit):
            if p.name_lower is not None:
                continue
            num_need_fixing += 1
            response['items'].append(dict(
                channel='fix-packages',
                url='/p/queue/fix-packages/r/%s' % p.key().name()))
                
        response['count'] = str(num_need_fixing)
        return response

class QueueFixPackages(QueueHandler):
    def respond_to_get(self, package_key_name):
        p = Package.get_by_key_name(package_key_name)
        p.name_lower = p.name.lower()
        p.save()
        return dict(successful=True)

class QueueAdmin(webapp.RequestHandler):
    def user_has_access(self):
        if self.request.environ.get('pypione.test.user_is_admin', False) == True:
            # this is lame
            return True
            
        user = users.get_current_user()
        if not user:
            self.redirect(users.create_login_url('/p/queue/admin'))
            return False
        if not users.is_current_user_admin():
            self.error(403)
            self.response.out.write("403")
            return False
        return True
            
    def get(self):
        if not self.user_has_access():
            return
        tpl = {
            'ipaddrs': [dict(ipaddr=ip.ipaddr, note=ip.note) for ip in QueueAllow.all().fetch(100)],
            'yours': self.request.environ.get('REMOTE_ADDR', None)}
        self.response.out.write(template.render(tpl_path('queue_admin.html'), tpl))
    
    def post(self):
        if not self.user_has_access():
            return
        QueueAllow(
            ipaddr=self.request.get("ipaddr"),
            note=self.request.get("note")).save()
        self.redirect("/p/queue/admin")
        return

class ShowPackage(webapp.RequestHandler):        
    def get(self, name, version):
        out = self.response.out.write
        packages = Package.all().filter("name =", name)
        if version:
            packages  = packages.filter("version =", version)
        packages = packages.order('-last_modified')
        tpl = {
            'name': name,
            'version': version,
            'release_files': [],
            'pypi_url': 'http://pypi.python.org/pypi/%s' % name,
        }
        if packages.count(1) == 0:
            self.error(404)
            self.response.out.write("404 Not Found")
            return
        results = packages.fetch(100)
        first_package = results[0]
        tpl['summary'] = getattr(first_package, 'summary', None)
        for package in results:
            pkg_files = PackageFile.all().filter("package =", package).order('filename')
            for pkg_file in pkg_files.fetch(100):
                tpl['release_files'].append(
                    (package.version, pkg_file.filename, pkg_file.md5_digest))
        self.response.out.write(template.render(tpl_path('package.html'), tpl))

    
class Upload(webapp.RequestHandler):
    def post(self):
        raise NotImplementedError("instead, upload to http://pypi.python.org/pypi/upload")
        
        self.response.headers['Content-type'] = 'text/plain'
        out = self.response.out.write
        out("\n")
        for key, val in self.request.POST.iteritems():
            if key=='content':
                pkg_info = read_pkg_info_from_tar(val.filename, val.value)
                val = "filename=%s; size=%s; pkg_info=%s" % (
                    val.filename, len(val.value), pkg_info)
            out("%-22s = %s\n" % (key, val))
        